tree grammar ConstCalc;

options {
  language = Java;
  output = AST;
  tokenVocab = TreeConstruction;
  ASTLabelType = InterAST;
  filter = true;
}

@header
{
  package ru.msu.cmc.sp.oberon2;
  
  import ru.msu.cmc.sp.oberon2.Symbol;
  import ru.msu.cmc.sp.oberon2.SymbolTable;
  import ru.msu.cmc.sp.oberon2.exceptions.*;
}

@members{
  SymbolTable symtab;
  String source; 
  Scope currentScope = null;
  public ConstCalc(TreeNodeStream input, SymbolTable symtab, String src) {
    this(input);
    this.symtab = symtab;
    this.source = src;
    currentScope = symtab.globals;
  }
}

topdown :
  exprRoot;

exprRoot:
  ^(EXPR ^(BYTECONST INTEGER)) -> ^(BYTECONST INTEGER)
  | ^(EXPR ^(SHORTCONST INTEGER)) -> ^(SHORTCONST INTEGER)
  | ^(EXPR ^(INTCONST INTEGER)) -> ^(INTCONST INTEGER)
  | ^(EXPR ^(LINTCONST INTEGER)) -> ^(LINTCONST INTEGER)
  | ^(EXPR ^(REALCONST REAL)) -> ^(REALCONST REAL)
  | ^(EXPR ^(BOOLCONST BOOLEAN)) -> ^(BOOLCONST BOOLEAN)
  | ^(EXPR ^(STRCONST STRING)) -> ^(STRCONST STRING)
  | ^(EXPR ^(CHARCONST CHAR)) ->  ^(CHARCONST CHAR)
  | ^(EXPR expr) -> ^(EXPR expr);

 
expr returns [String value, Type type]
@init{
String res = "";
int type = 0;
Symbol sym = null;
} :
  ^(EXPR a=expr) { $value = $a.value; $type = $a.type; }
  | ^(SHORTCONST INTEGER) { $value = $INTEGER.text; $type = SymbolTable._shortint; }
  | ^(INTCONST INTEGER) { $value = $INTEGER.text; $type = SymbolTable._int; }
  | ^(LINTCONST INTEGER) { $value = $INTEGER.text; $type = SymbolTable._longint; } 
  | ^(REALCONST REAL) { $value = $REAL.text; $type = SymbolTable._real; }
  | ^(BOOLCONST BOOLEAN) { $value = $BOOLEAN.text; $type = SymbolTable._boolean; }
  | ^(STRCONST  STRING) { $value = $STRING.text; $type = new ArrayType(SymbolTable._char, -1); }
  | ^(CHARCONST CHAR) { $value = $CHAR.text; $type = SymbolTable._char; }
//FIXME toogle
  | IDENT { sym = currentScope.resolve($IDENT.text);
     if (sym != null) {
        res = sym.value;
        $value = sym.value;
        $type = sym.type;
     } else {
        res = null;
        $value = "";
     }
    }
    -> {res == null}? IDENT
    -> {sym.type == SymbolTable._boolean}? ^(BOOLCONST BOOLEAN[res]) 
    -> {sym.type == SymbolTable._char}? ^(CHARCONST CHAR[res])   
    -> {sym.type == SymbolTable._shortint}? ^(SHORTCONST INTEGER[res])  
    -> {sym.type == SymbolTable._int}? ^(INTCONST INTEGER[res])  
    -> {sym.type == SymbolTable._longint}? ^(LINTCONST INTEGER[res])  
    -> {sym.type == SymbolTable._real}? ^(REALCONST REAL[res])  
    -> {sym.type == SymbolTable._longreal}? ^(LREALCONST REAL[res]) 
    -> ^(STRCONST STRING[res]) 
  | ^(BUILTINFUNC 'MAX' tt = type) {
     if ($tt.type.isNumber()) {
       if ($tt.type == SymbolTable._shortint) res = "32767";
       if ($tt.type == SymbolTable._int) res = "2147483647";
       if ($tt.type == SymbolTable._longint) res = "9223372036854775807";
       if ($tt.type == SymbolTable._real) res = "3.40282E+38";
       if ($tt.type == SymbolTable._longreal) res = "1.79769313486E+308";
       $value = res;   
       $type = $tt.type;                  
  } else {
     res = null;
     $value = "";
  }
  }
    -> {res == null}? ^(BUILTINFUNC 'MAX' type)
    -> {$tt.type == SymbolTable._shortint}? ^(SHORTCONST INTEGER[res])  
    -> {$tt.type == SymbolTable._int}? ^(INTCONST INTEGER[res])  
    -> {$tt.type == SymbolTable._longint}? ^(LINTCONST INTEGER[res])  
    -> {$tt.type == SymbolTable._real}? ^(REALCONST REAL[res])  
    -> {$tt.type == SymbolTable._longreal}? ^(LREALCONST REAL[res]) 
    -> ^(INTCONST INTEGER["FAIL"])
  | ^(BUILTINFUNC 'MIN' tt = type){
     if ($tt.type != null && $tt.type.isNumber()) {
       if ($tt.type == SymbolTable._shortint) res = "-32768";
       if ($tt.type == SymbolTable._int) res = "-2147483648";
       if ($tt.type == SymbolTable._longint) res = "-9223372036854775808";
       if ($tt.type == SymbolTable._real) res = "-3.40282E+38";
       if ($tt.type == SymbolTable._longreal) res = "-1.79769313486E+308";
       $value = res;    
       $type = $tt.type;                         
  } else {
     res = null;
     $value = "";
  }
  }
    -> {res == null}? ^(BUILTINFUNC 'MIN' type)
    -> {$tt.type == SymbolTable._shortint}? ^(SHORTCONST INTEGER[res])  
    -> {$tt.type == SymbolTable._int}? ^(INTCONST INTEGER[res])  
    -> {$tt.type == SymbolTable._longint}? ^(LINTCONST INTEGER[res])  
    -> {$tt.type == SymbolTable._real}? ^(REALCONST REAL[res])  
    -> {$tt.type == SymbolTable._longreal}? ^(LREALCONST REAL[res]) 
    -> ^(INTCONST INTEGER["FAIL"])
  | ^(BUILTINFUNC 'SIZE' tt = type) {
     if ($tt.type != null && $tt.type.isNumber()) {
       if ($tt.type == SymbolTable._boolean) res = "1";
       if ($tt.type == SymbolTable._char) res = "8";
       if ($tt.type == SymbolTable._shortint) res = "16";
       if ($tt.type == SymbolTable._int) res = "32";
       if ($tt.type == SymbolTable._longint) res = "64";
       if ($tt.type == SymbolTable._real) res = "32";
       if ($tt.type == SymbolTable._longreal) res = "64";
       $value = res;
       $type = SymbolTable._int;                          
  } else {
     res = null;
     $value = "";
  }
  }
    -> ^(INTCONST INTEGER[res])
  | ^(BUILTINFUNC 'VAL' type expr) {throw new RecognitionError($BUILTINFUNC, "Not implemented");}
  | ^(BUILTINFUNC 'ABS' a =expr) {
     if ($a.type == null || $a.type.isNumber()) {
       res = $a.value;
       if (res.charAt(0) == '-') res = res.substring(1);
       $value = res; 
       $type = $a.type;                      
  } else {
     res = null;
     $value = "";
  }
  }
    -> {res == null}? ^(BUILTINFUNC 'ABS' expr)
    -> {$a.type == SymbolTable._shortint}? ^(SHORTCONST INTEGER[res])  
    -> {$a.type == SymbolTable._int}? ^(INTCONST INTEGER[res])  
    -> {$a.type == SymbolTable._longint}? ^(LINTCONST INTEGER[res])  
    -> {$a.type == SymbolTable._real}? ^(REALCONST REAL[res])  
    -> {$a.type == SymbolTable._longreal}? ^(LREALCONST REAL[res]) 
    -> ^(INTCONST INTEGER[res])
  | ^(BUILTINFUNC 'ASH' a = expr b = expr) {
     if ($a.type.isInt() && $b.type.isInt()) {
       long x  = Long.parseLong($a.value);
       long n  = Long.parseLong($b.value);
       res = String.valueOf(x << n);
       $value = res;
       $type = SymbolTable._longint;                      
  } else {
     res = null;
     $value = "";
  }
  }
    -> {res == null}? ^(BUILTINFUNC 'ASH' expr expr)
    -> ^(LINTCONST INTEGER[res])
  | ^(BUILTINFUNC 'CHR' expr)  {throw new RecognitionError($BUILTINFUNC, "Not implemented");}
  | ^(BUILTINFUNC 'ENTIER' expr)  {throw new RecognitionError($BUILTINFUNC, "Not implemented");}
  | ^(BUILTINFUNC 'EVEN' expr)  {throw new RecognitionError($BUILTINFUNC, "Not implemented");}
  | ^(BUILTINFUNC 'LEN' a = expr) {
     if ($a.type instanceof ArrayType && ((ArrayType) $a.type).elementType == SymbolTable._char) {
       res = String.valueOf(($a.value.length() - 2));
       $value = res;
       $type = SymbolTable._longint;   
     } else {
       res = null;
       $value = "";
     }
  }
    -> {res == null}? ^(BUILTINFUNC 'LEN' expr)
    -> ^(LINTCONST INTEGER[res])
  | ^(BUILTINFUNC 'LSH' expr expr) {throw new RecognitionError($BUILTINFUNC, "Not implemented");}
  | ^(BUILTINFUNC 'ODD' expr)  {throw new RecognitionError($BUILTINFUNC, "Not implemented");}
  | ^(BUILTINFUNC 'ORD' expr) {throw new RecognitionError($BUILTINFUNC, "Not implemented");}
  | ^(PLUS a=expr b=expr) 
    {
	    if (a != null && b != null && $a.value != "" && $b.value != "") { 
	       if ($a.type.isNumber() && $b.type.isNumber()) {
             try {
	            res = String.valueOf(Integer.parseInt($a.value) + Integer.parseInt($b.value));
	            $type = SymbolTable._int;
             } catch (NumberFormatException e) {
              res = String.valueOf(Double.parseDouble($a.value) + Double.parseDouble($b.value));
              $type = SymbolTable._real;
             }
            $value = res;
         } else {
           throw new RecognitionError($PLUS, "Operation not supported for this type");
         }
      } else {
       res = null;
       $value = "";
      }
    }
    -> {res == null}? ^(PLUS expr expr) 
    -> {$type.equals(SymbolTable._int)}? ^(INTCONST INTEGER[res])
    -> {$type.equals(SymbolTable._real)}? ^(REALCONST REAL[res])
    -> {$type.isString()}? ^(STRCONST STRING[res])
    -> ^(PLUS expr expr) 
    | ^(MINUS a=expr b=expr) 
    {
      if (a != null && b != null && $a.value != "" && $b.value != "") {
      if ($a.type.isNumber() && $b.type.isNumber()) {
             try {
              res = String.valueOf(Integer.parseInt($a.value) - Integer.parseInt($b.value));
              $type = SymbolTable._int;
             } catch (NumberFormatException e) {
              res = String.valueOf(Double.parseDouble($a.value) - Double.parseDouble($b.value));
              $type = SymbolTable._real;
              type = 1;
             }
         } else {
           throw new RecognitionError($MINUS, "Operation not supported for this type");
         }
         $value = res;
      } else {
       res = null;
       $value = "";
      }
    }  
    -> {res == null}? ^(MINUS expr expr) 
    -> {type == 0}? ^(INTCONST INTEGER[res])
    -> ^(REALCONST REAL[res])
    | ^(STAR a=expr b=expr) 
    {
      if (a != null && b != null && $a.value != "" && $b.value != "") { 
      if ($a.type.isNumber() && $b.type.isNumber()) {
             try {
              res = String.valueOf(Integer.parseInt($a.value) * Integer.parseInt($b.value));
              $type = SymbolTable._int;
             } catch (NumberFormatException e) {
              res = String.valueOf(Double.parseDouble($a.value) * Double.parseDouble($b.value));
              $type = SymbolTable._real;
              type = 1;
             }
         } else {
           throw new RecognitionError($STAR, "Operation not supported for this type");
         }
         $value = res;
      } else {
       res = null;
       $value = "";
      }
    }  
    -> {res == null}? ^(STAR expr expr) 
    -> {type == 0}? ^(INTCONST INTEGER[res])
    -> ^(REALCONST REAL[res])
    | ^(q = 'DIV' a=expr b=expr) 
    {
      
      if (a != null && b != null && $a.value != "" && $b.value != "") {
      if ($a.type.isNumber() && $b.type.isNumber()) {
      if ( Integer.parseInt($b.value) == 0){
        throw new RecognitionException();
      }
      } else {
           throw new RecognitionError($q, "Operation not supported for this type");
      }
       res = String.valueOf(Integer.parseInt($a.value) / Integer.parseInt($b.value));
       $type = SymbolTable._int;
       $value = res;
      } else {
       res = null;
       $value = "";
      }
    }
    -> {res == null}? ^('DIV' expr expr) 
    -> ^(INTCONST INTEGER[res])
    | ^(q ='MOD' a=expr b=expr) 
    {
      
      if (a != null && b != null && $a.value != "" && $b.value != "") {
      if ($a.type.isNumber() && $b.type.isNumber()) {
      if ( Integer.parseInt($b.value) == 0){
        throw new RecognitionException();
      }
      } else {
           throw new RecognitionError($q, "Operation not supported for this type");
         }
       res = String.valueOf(Integer.parseInt($a.value) - (Integer.parseInt($a.value) / Integer.parseInt($b.value))*Integer.parseInt($b.value));
       $type = SymbolTable._int;
         $value = res;
      } else {
       res = null;
       $value = "";
      }
    }  
    -> {res == null}? ^('MOD' expr expr) 
    -> ^(INTCONST INTEGER[res])
    | ^(SLASH a=expr b=expr) 
    {
      if (a != null && b != null && $a.value != "" && $b.value != "") {
      if ($a.type.isNumber() && $b.type.isNumber()) { 
             try {
              res = String.valueOf((double) Integer.parseInt($a.value) / Integer.parseInt($b.value));
             } catch (NumberFormatException e) {
              res = String.valueOf(Double.parseDouble($a.value) / Double.parseDouble($b.value));
              }
         } else {
           throw new RecognitionError($SLASH, "Operation not supported for this type");
         }
         $type = SymbolTable._real;
         $value = res;
      } else {
       res = null;
       $value = "";
      }
    }
    -> {res == null}? ^(SLASH expr expr) 
    -> ^(REALCONST REAL[res])
    | ^(AMP a=expr b=expr) 
    {
      if (a != null && b != null && $a.value != "" && $b.value != "") { 
            res = String.valueOf(Boolean.valueOf($a.value) && Boolean.valueOf($b.value)).toUpperCase() ;
            $type = SymbolTable._boolean;
         $value = res;
      } else {
       res = null;
       $value = "";
      }
    }
    -> {res == null}? ^(AMP expr expr) 
    -> ^(BOOLCONST BOOLEAN[res])
    | ^(OR a=expr b=expr) 
    {
      if (a != null && b != null && $a.value != "" && $b.value != "") { 
            res = String.valueOf(Boolean.valueOf($a.value) || Boolean.valueOf($b.value)).toUpperCase() ;  
         $value = res;
         $type = SymbolTable._boolean;
      } else {
       res = null;
       $value = "";
      }
    }
    -> {res == null}? ^('OR' expr expr) 
    -> ^(BOOLCONST BOOLEAN[res])
    | ^(TILDA a=expr) 
    {
      if (a != null && $a.value != "") { 
            res = String.valueOf(!Boolean.valueOf($a.value)).toUpperCase() ;  
         $value = res;
         $type = SymbolTable._boolean;
      } else {
       res = null;
       $value = "";
      }
      }
    -> {res == null}? ^(TILDA expr) 
    -> ^(BOOLCONST BOOLEAN[res])
//EQUAL : '=' ;
//LESS : '<' ;
//LEQ : '<=' ;
//GREATER : '>' ;
//GEQ : '>=' ;
    | ^(EQUAL a=expr b=expr) 
    {
      if (a != null && b != null && $a.value != "" && $b.value != "") { 
          System.out.println($a.value + " = " + $b.value);
         try {
           res = String.valueOf(Integer.parseInt($a.value) == Integer.parseInt($b.value));
         } catch (NumberFormatException e) {
           res = String.valueOf(Double.parseDouble($a.value) == Double.parseDouble($b.value));
         } 
         $type = SymbolTable._boolean;
         res = res.toUpperCase();
         $value = res;
      } else {
       res = null;
       $value = "";
      }
    }
    -> {res == null}? ^(EQUAL expr expr) 
    -> ^(BOOLCONST BOOLEAN[res])
    | ^(LESS a=expr b=expr) 
    {
      if (a != null && b != null && $a.value != "" && $b.value != "") { 
         try {
           res = String.valueOf(Integer.parseInt($a.value) < Integer.parseInt($b.value));
         } catch (NumberFormatException e) {
           res = String.valueOf(Double.parseDouble($a.value) < Double.parseDouble($b.value));
         }  
         $type = SymbolTable._boolean;
         res = res.toUpperCase();
         $value = res;
      } else {
       res = null;
       $value = "";
      }
    }
    -> {res == null}? ^(LESS expr expr) 
    -> ^(BOOLCONST BOOLEAN[res])
    | ^(LEQ a=expr b=expr) 
    {
      if (a != null && b != null && $a.value != "" && $b.value != "") { 
         try {
           res = String.valueOf(Integer.parseInt($a.value) <= Integer.parseInt($b.value));
         } catch (NumberFormatException e) {
           res = String.valueOf(Double.parseDouble($a.value) <= Double.parseDouble($b.value));
         } 
         $type = SymbolTable._boolean;
         res = res.toUpperCase(); 
         $value = res;
      } else {
       res = null;
       $value = "";
      }
    }
    -> {res == null}? ^(LEQ expr expr) 
    -> ^(BOOLCONST BOOLEAN[res])
    | ^(GREATER a=expr b=expr) 
    {
      if (a != null && b != null && $a.value != "" && $b.value != "") { 
         try {
           res = String.valueOf(Integer.parseInt($a.value) > Integer.parseInt($b.value));
         } catch (NumberFormatException e) {
           res = String.valueOf(Double.parseDouble($a.value) > Double.parseDouble($b.value));
         }
         $type = SymbolTable._boolean;
         res = res.toUpperCase();
         $value = res;
      } else {
       res = null;
       $value = "";
      }
    }
    -> {res == null}? ^(GREATER expr expr) 
    -> ^(BOOLCONST BOOLEAN[res])
    | ^(GEQ a=expr b=expr) 
    {
      if (a != null && b != null && $a.value != "" && $b.value != "") { 
         try {
           res = String.valueOf(Integer.parseInt($a.value) >= Integer.parseInt($b.value));
         } catch (NumberFormatException e) {
           res = String.valueOf(Double.parseDouble($a.value) >= Double.parseDouble($b.value));
         }
         $type = SymbolTable._boolean;
         res = res.toUpperCase();
         $value = res;
      } else {
       res = null;
       $value = "";
      }
    }
    -> {res == null}? ^(GEQ expr expr) 
    -> ^(BOOLCONST BOOLEAN[res]);
    
    
type returns [Type type]
  : ^(ARRAY (n = inumber)? typeElement) //warning, but it works!
    {
        int num;
        if ($n.start == null) num = -1;
        else num = Integer.parseInt($n.value);
        $type = new ArrayType($typeElement.type, num);
     }
  | typeElement {$type = $typeElement.type;}
  ;
  
  
typeElement returns [Type type]
@init {InterAST t = (InterAST)input.LT(1);}
@after {
    t.symbol = currentScope.resolve(t.getText());
    t.scope = currentScope;
    $type = (Type)t.symbol;
    if ($type == null) 
    {throw new RecognitionError(0,0, "Unknown type " + t.getText());}
}
    :   'REAL'
    |   'BYTE'
    |   'INTEGER'
    |   'LONGINT'
    |   'SHORTINT'
    |   'LONGREAL'
    |   'CHAR'
    |   'BOOLEAN'
    |   IDENT
    ;
    
inumber returns [String value, Type type]
    : ^(INTCONST INTEGER)   { $value = $INTEGER.text; $type = SymbolTable._int;} 
    | ^(SHORTCONST INTEGER) { $value = $INTEGER.text; $type = SymbolTable._shortint;}   
    | ^(LINTCONST INTEGER)  { $value = $INTEGER.text; $type = SymbolTable._longint;}  
    ;  
